"""
PDF document processing core module.
"""

import os
from typing import List, Dict, Optional, Tuple, Any
from pathlib import Path
import logging
from dataclasses import dataclass

import fitz  # PyMuPDF
from PyQt6.QtCore import QObject, pyqtSignal, QThread, QTimer
from PyQt6.QtGui import QPixmap, QImage


@dataclass
class PageInfo:
    """Information about a PDF page."""
    index: int
    width: float
    height: float
    rotation: int


@dataclass
class PDFMetadata:
    """PDF document metadata."""
    title: Optional[str] = None
    author: Optional[str] = None
    subject: Optional[str] = None
    creator: Optional[str] = None
    producer: Optional[str] = None
    creation_date: Optional[str] = None
    modification_date: Optional[str] = None
    page_count: int = 0
    file_size: int = 0
    encrypted: bool = False


class PDFLoadWorker(QObject):
    """Worker for loading PDF in background thread."""
    finished = pyqtSignal(object, str)  # PDFProcessor, error
    progress = pyqtSignal(int)  # Progress percentage

    def __init__(self, file_path: str):
        super().__init__()
        self.file_path = file_path
        self._is_cancelled = False

    def cancel(self):
        """Cancel the loading operation."""
        self._is_cancelled = True

    def run(self):
        """Load the PDF document."""
        try:
            if self._is_cancelled:
                self.finished.emit(None, "Cancelled")
                return

            processor = PDFProcessor()
            processor.load_document(self.file_path, self.progress.emit)

            if not self._is_cancelled:
                self.finished.emit(processor, "")
            else:
                processor.close()
                self.finished.emit(None, "Cancelled")
        except Exception as e:
            if not self._is_cancelled:
                self.finished.emit(None, str(e))


class PDFProcessor(QObject):
    """Core PDF document processing class."""

    def __init__(self):
        super().__init__()
        self._document: Optional[fitz.Document] = None
        self._file_path: Optional[str] = None
        self._metadata: PDFMetadata = PDFMetadata()
        self._page_cache: Dict[str, QPixmap] = {}  # 使用字符串键
        self._max_cache_size = 5  # 极端减少缓存到5个页面以最小化内存
        self._cache_access_order = []  # 跟踪访问顺序
        self._logger = logging.getLogger(__name__)

    def load_document(self, file_path: str, progress_callback=None) -> bool:
        """Load a PDF document from file.

        Args:
            file_path: Path to the PDF file
            progress_callback: Callback function for progress updates

        Returns:
            True if successful, False otherwise
        """
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"File not found: {file_path}")

        if not file_path.lower().endswith('.pdf'):
            raise ValueError("Invalid file format. Only PDF files are supported.")

        try:
            # Close existing document
            self.close()

            # Open new document
            self._document = fitz.open(file_path)
            self._file_path = file_path

            # Extract metadata
            self._extract_metadata()

            # Clear cache and access order
            self._page_cache.clear()
            self._cache_access_order.clear()

            # Generate page count
            page_count = self._document.page_count
            if progress_callback:
                progress_callback(50)

            # Preload first few thumbnails
            preload_count = min(5, page_count)
            for i in range(preload_count):
                if progress_callback:
                    progress_callback(50 + (i + 1) * 10 / preload_count)
                self.get_page_thumbnail(i)

            if progress_callback:
                progress_callback(100)

            self._logger.info(f"Successfully loaded PDF: {file_path}")
            return True

        except Exception as e:
            self._logger.error(f"Failed to load PDF: {e}")
            self.close()
            raise RuntimeError(f"Failed to load PDF document: {e}")

    def close(self):
        """Close the current document."""
        if self._document:
            self._document.close()
            self._document = None
        self._file_path = None
        self._page_cache.clear()
        self._cache_access_order.clear()

    def is_loaded(self) -> bool:
        """Check if a document is loaded."""
        return self._document is not None

    def get_page_count(self) -> int:
        """Get the number of pages in the document."""
        return self._document.page_count if self._document else 0

    def get_page_info(self, index: int) -> Optional[PageInfo]:
        """Get information about a specific page."""
        if not self._document or index < 0 or index >= self._document.page_count:
            return None

        page = self._document[index]
        rect = page.rect
        return PageInfo(
            index=index,
            width=rect.width,
            height=rect.height,
            rotation=page.rotation
        )

    def get_metadata(self) -> PDFMetadata:
        """Get document metadata."""
        return self._metadata

    def get_outline(self) -> List[Dict[str, Any]]:
        """Get the document outline (bookmarks).

        Returns:
            List of bookmarks with title, page number, and level
        """
        if not self._document:
            return []

        outline = []

        try:
            # Get the outline using PyMuPDF's get_toc() method
            toc = self._document.get_toc()

            for item in toc:
                # item is a list: [level, title, page_number, ...]
                if len(item) >= 3:
                    level = item[0] - 1  # Convert to 0-based level
                    title = item[1]
                    page = item[2]

                    outline.append({
                        'title': title,
                        'page': page - 1,  # Convert to 0-based page index
                        'level': level,
                        'type': 'page'
                    })

        except Exception as e:
            self._logger.warning(f"Failed to extract outline: {e}")

        return outline

    def render_page_thumbnail(self, index: int, size: Tuple[int, int] = (200, 200)) -> Optional[QPixmap]:
        """Render a page thumbnail.

        Args:
            index: Page index
            size: Thumbnail size (width, height)

        Returns:
            QPixmap of the thumbnail or None if failed
        """
        if not self._document or index < 0 or index >= self._document.page_count:
            return None

        # Check cache first (LRU implementation)
        cache_key = f"{index}_{size[0]}_{size[1]}"
        if cache_key in self._page_cache:
            # Move to end (most recently used)
            self._cache_access_order.remove(cache_key)
            self._cache_access_order.append(cache_key)
            return self._page_cache[cache_key]

        try:
            page = self._document[index]

            # Calculate zoom factor
            page_rect = page.rect
            zoom_x = size[0] / page_rect.width
            zoom_y = size[1] / page_rect.height
            zoom = min(zoom_x, zoom_y)

            # Render page with better quality
            matrix = fitz.Matrix(zoom, zoom)
            # 使用优化的渲染参数以平衡质量和内存
            pix = page.get_pixmap(
                matrix=matrix,
                alpha=False,
                dpi=96,  # 进一步降低DPI到96，标准显示器DPI
                clip=page.rect
            )

            # Convert to QPixmap with high quality
            img = QImage(pix.samples, pix.width, pix.height, pix.stride, QImage.Format.Format_RGB888)
            pixmap = QPixmap.fromImage(img)
            # Enable smooth transformation for better scaling
            pixmap.setDevicePixelRatio(1.0)  # 确保正确的像素比

            # LRU Cache management
            if len(self._page_cache) >= self._max_cache_size:
                # Remove least recently used cache entry
                lru_key = self._cache_access_order.pop(0)
                del self._page_cache[lru_key]

            # Cache the result and track access order
            self._page_cache[cache_key] = pixmap
            self._cache_access_order.append(cache_key)

            return pixmap

        except Exception as e:
            self._logger.error(f"Failed to render thumbnail for page {index}: {e}")
            return None

    def get_page_thumbnail(self, index: int, size: Tuple[int, int] = (200, 200)) -> Optional[QPixmap]:
        """Get a cached page thumbnail or render it if not cached.

        Args:
            index: Page index
            size: Thumbnail size (width, height)

        Returns:
            QPixmap of the thumbnail
        """
        return self.render_page_thumbnail(index, size)

    def get_page_text(self, index: int) -> str:
        """Extract text from a page.

        Args:
            index: Page index

        Returns:
            Text content of the page
        """
        if not self._document or index < 0 or index >= self._document.page_count:
            return ""

        try:
            page = self._document[index]
            return page.get_text()
        except Exception as e:
            self._logger.error(f"Failed to extract text from page {index}: {e}")
            return ""

    def _extract_metadata(self):
        """Extract metadata from the loaded document."""
        if not self._document:
            return

        # Get basic document info
        self._metadata.page_count = self._document.page_count

        # Get file size
        if self._file_path and os.path.exists(self._file_path):
            self._metadata.file_size = os.path.getsize(self._file_path)

        # Check if encrypted
        self._metadata.encrypted = self._document.is_encrypted

        # Extract metadata dictionary
        metadata = self._document.metadata
        self._metadata.title = metadata.get('title')
        self._metadata.author = metadata.get('author')
        self._metadata.subject = metadata.get('subject')
        self._metadata.creator = metadata.get('creator')
        self._metadata.producer = metadata.get('producer')
        self._metadata.creation_date = metadata.get('creationDate')
        self._metadata.modification_date = metadata.get('modDate')

    def get_file_path(self) -> Optional[str]:
        """Get the current file path."""
        return self._file_path